package com.example.mq.mqserver.core;

import com.example.mq.common.MqException;

import java.util.WeakHashMap;

/**
 * 使用这个类，来实现交换机的转发规则
 * 同时也借助这个类 验证 bindingKey/routingKey 是否合法
 */
public class Router {
    public boolean checkBindingKey(String bindingKey) {
        // bindingKey的构造规则
        // 1. 数字、字母、下划线
        // 2. 使用 . 进行分割
        // 3. 允许存在 * 和 #  作为通配符，但是只能作为独立的存在
        if (bindingKey.length() == 0) {
            // 空字符串，也是合法情况，比如使用 DIRECT 或者 FANOUT， bindingKey 是用不上
            return true;
        }
        // 检查字符串中不存在非法字符
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            // 判定该字母是否是大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判定该字母是否是小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判定该字母是否是阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 判定是否是 _ 或者 .
            if (ch == '_' || ch == '.' || ch == '#' || ch == '*') {
                continue;
            }
            return false;
        }
        // 检查 *  # 是否是独立的部分
        // 由于 . 在正则表达式中是一种特殊符号，需要转义； 用 \. 但是在 Java中这又是个特殊字符；所以要用 \\.
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
            // 检查 word 长度 是否大于1，并且包含了 * 或者 # ，就是非法
            if (word.length() > 1 && (word.contains("#") || word.contains("*"))) {
                return false;
            }
        }
        // 约定下，通配符之间的相邻关系
        // 为啥这么约定？因为前三种相邻的时候，实现逻辑非常繁琐，同时功能性提升不大
        // 1. aaa.#.#.bbb  -> 非法
        // 2. aaa.#.*.bbb  -> 非法
        // 3. aaa.*.#.bbb  -> 非法
        // 4. aaa.*.*.bbb  -> 合法
        for (int i = 0; i < words.length - 1; i++) {
            // 连续两个 #
            if (words[i].equals("#") && words[i].equals("#")) {
                return false;
            }
            // # *
            if (words[i].equals("#") && words[i].equals("*")) {
                return false;
            }
            // * #
            if (words[i].equals("*") && words[i].equals("#")) {
                return false;
            }
        }
        return true;
    }

    // routingKey的构造规则
    // 1. 数字、字母、下划线
    // 2. 使用 . 分割成若干个部分
    public boolean checkRoutingKey(String routingKey) {
        if (routingKey.length() == 0) {
            // 空字符串，合法情况。比如使用 FANOUT 交换机的时候，routingKey 用不上，就可以设置成 ""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 判定该字母是否是大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判定该字母是否是小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判定该字母是否是阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 判定是否是 _ 或者 .
            if (ch == '_' || ch == '.') {
                continue;
            }
            // 该字符不是上述任何一种，就不合法，直接返回 false
            return false;
        }
        return true;
    }

    // 这个方法用来判定 该消息是否可以转发给绑定对应的队列
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        // 根据不同的 exchangeType 使用不同的判定规则
        if (exchangeType == ExchangeType.FANOUT) {
            // 如果是 FANOUT类型，则该交换机绑定的所有队列都要转发
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            return routeTopic(binding, message);
        } else {
            // 其他情况不应该存在
            throw new MqException("[Router] 交换机类型非法！exchangeType=" + exchangeType);
        }
    }

    // 实现匹配规则
    private boolean routeTopic(Binding binding, Message message) {
        // 先把两个 Key 进行拆分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");
        // 引入两个下标，指向两个数组，初始情况下都为 0
        int bindingIndex = 0;
        int routingIndex = 0;
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            // 【情况二】 遇到 *
            if (bindingTokens[bindingIndex].equals("*")) {
                bindingIndex++;
                routingIndex++;
                // 【情况三】 遇到 #
            } else if (bindingTokens[bindingIndex].equals("#")) {
                bindingIndex++;
                // 【情况四】 # 后面没有内容
                if (bindingIndex == bindingTokens.length) {
                    // 说明该 # 后面没有东西了 匹配成功
                    return true;
                }
                // 后面还有东西，拿着这个内容去 routingTokens 中找，找到对应位置
                // 使用 findNextMatch 这个方法用来查找该部分 在 routingTokens 中的位置，并返回下标; 没找到返回 -1
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                if (routingIndex == -1) {
                    // 没找到匹配结果，返回 false
                    return false;
                }
                // 找到匹配结果，继续往下匹配
                bindingIndex++;
                routingIndex++;
            } else {
                // 【情况一】普通字符串需要一模一样
                if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        // 判定双方是否同时到达末尾 【情况五】
        if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingElement) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if (routingTokens[i].equals(bindingElement)) {
                return i;
            }
        }
        return -1;
    }
}
